"""Provides functions dealing with random numbers"""

import math
import random
from pyre.core import types

def roll_xdy(xdy):
    """
    roll_xdy(tuple of int): int
    Returns the result of an xdy dice roll
    """

    return sum(random.randint(1, xdy[1]) for _ in range(xdy[0]))


def roll_dy(y):
    """
    roll_dy(int): int
    Returns the result of a y-sided dice roll
    """

    return random.randint(1, y)

def roll_d20():
    """
    roll_d20(): int
    Returns the result of a 20-sided dice roll
    """

    return random.randint(1, 20)


def create_opposed(y):
    """
    create_opposed(int): func
    Creates a function which generates a 1dy - 1dy roll for skill checks
    """

    return lambda: random.randint(1, 10) - random.randint(1, 10)


def roll_d10_opposed():
    """
    roll_d10_opposed(): int
    Returns the result of a d10 roll, minus the result of another d10
    """

    return random.randint(1, 10) - random.randint(1, 10)




def rand_coordinate(startx, starty, stopx=None, stopy=None):
    """
    rand_coordinate(int, int, int, int): Coordinate
    Returns a random Coordinate within the given bounds
    """

    return types.Coordinate(random.randrange(startx, stopx),
                            random.randrange(starty, stopy))


def rand_direction(include_origin=False):
    """
    rand_direction(bool): Coordinate
    Returns a random cardinal direction represented as a Coordinate. The origin
    can only be sampled if specified
    """

    x, y = random.randint(-1, 1), random.randint(-1, 1)
    if not include_origin:
        while x == 0 and y == 0:
            x, y = random.randint(-1, 1), random.randint(-1, 1)
    return types.Coordinate(x, y)


def choice(items):
    """
    choice(iterable): object
    Returns a random object from the given items, even if the container does
    not allow random access. However, sampling time is linear with size.
    """

    return random.choice(list(items))


def coinflip():
    """
    coinflip(): bool
    Returns True with probability .5
    """

    return not random.getrandbits(1)


def chance(probability):
    """
    chance(float): bool
    Returns True with the given probability
    """

    return random.random() < probability


def sample_categorical(weights):
    """
    sample_categorical(dict): object
    Returns a sample from the unnormalized count dictionary
    """

    sample = random.uniform(0, sum(weights.values()))
    for item, weight in weights.iteritems():
        if sample < weight:
            return item
        sample -= weight


def sample_poisson(rate):
    """
    sample_poisson(float): int
    Returns a sample from a Poisson distribution with the given rate using the
    algorithm given by Knuth. Note this is unstable and slow for large rates.
    """

    invrate = math.exp(-rate)
    p = random.random()
    k = 0
    while p > invrate:
        k += 1
        p *= random.random()
    return k
